const assert = require("assert");

const Eva = require("./Eva");

const eva = new Eva();

//----------------------------------
// Tests:

assert.strictEqual(eva.eval(1), 1);
assert.strictEqual(eva.eval('"hello"'), "hello");

// Math:
assert.strictEqual(eva.eval(["+", 2, 3]), 5);
assert.strictEqual(eva.eval(["+", ["+", 2, 3], 5]), 10);
assert.strictEqual(eva.eval(["*", ["*", 2, 3], 5]), 30);

// Variables:
assert.strictEqual(eva.eval(["var", "x", 10]), 10);
assert.strictEqual(eva.eval("x"), 10);
assert.strictEqual(eva.eval(["var", "_x", 10]), 10);
assert.strictEqual(eva.eval("_x"), 10);

assert.strictEqual(eva.eval("VERSION"), "0.1.0");

// var isOk = true; var a = 2 * 3;
assert.strictEqual(eva.eval(["var", "isOk", "true"]), true);
assert.strictEqual(eva.eval(["var", "a", ["*", 2, 3]]), 6);
assert.strictEqual(eva.eval("a"), 6);

// Blocks:
assert.strictEqual(
  eva.eval([
    "begin",

    ["var", "x", 10],
    ["var", "y", 20],

    ["+", ["*", "x", "y"], 50],
  ]),
  250
);
assert.strictEqual(
  eva.eval([
    "begin",
    ["var", "x", 10],
    [
      "begin",
      // var x = 20;
      ["var", "x", 20],
      "x",
    ],
    "x",
  ]),
  10
);
assert.strictEqual(
  eva.eval([
    "begin",
    ["var", "value", 10],

    [
      "var",
      "result",
      [
        "begin",
        // var x = value + 10
        ["var", "x", ["+", "value", 10]],
        "x",
      ],
    ],

    "result",
  ]),
  20
);

// (var x 10)
// (print x) // 10
// (begin
//   (set x 20)
//   (print x) // 20
// )
// (print x) // 20
assert.strictEqual(
  eva.eval([
    "begin",
    ["var", "data", 10],

    ["begin", ["set", "data", 100]],

    "data",
  ]),
  100
);

/**
 * (if <condition>
 *  <consequent>
 *  <alternate>)
 */

assert.strictEqual(
  eva.eval([
    "begin",
    ["var", "x", 10],
    ["var", "y", 0],

    [
      "if",
      ["<", "x", 10],
      // y = 10
      ["set", "y", 10],
      // y = 30
      ["set", "y", 30],
    ],

    "y",
  ]),
  30
);

assert.strictEqual(
  eva.eval([
    "begin",
    ["var", "counter", 0],
    ["var", "result", 0],
    [
      "while",
      ["<", "counter", 10],

      [
        "begin",
        // result++
        ["set", "result", ["+", "result", 1]],
        // counter++
        ["set", "counter", ["+", "counter", 1]],
      ],
    ],
    "result",
  ]),
  10
);

// evaParser.js 通过 npm run parse-output 生成
const evaParser = require("./evaParser");
function testFn(eva, code, expected) {
  const exp = evaParser.parse(code);
  assert.strictEqual(eva.eval(exp), expected);
}

testFn(
  eva,
  `
  (begin
    (var x 10)
    (var y 20)
    (+ (* x 10) y)
  )
`,
  120
);

testFn(eva, `(+ 1 5)`, 6);
testFn(eva, `(+ (+ 2 3) 5)`, 10);
testFn(eva, `(+ (* 2 3) 5)`, 11);

testFn(eva, `(> 1 5)`, false);

testFn(eva, `(>= 5 5)`, true);
testFn(eva, `(= 5 5)`, true);
// Navitive function: print
eva.eval(["print", '"Hello"', '"World!!!"']);

// User-defined function:
testFn(
  eva,
  `
(begin
  (def square (x)
    (* x x)
  )
  (square 2)
)
`,
  4
);

testFn(
  eva,
  `
(begin
  (def calc (x y)
    (begin 
      (var z  30)
      (+ (* x y) z)  
    )
  )
  (calc 10 20)
)
`,
  230
);

// Closure：
testFn(
  eva,
  `
(begin
  (var value 100)
  (def calc (x y)
    (begin 
      (var z (+ x y))
      (def inner (foo)
        (+ (+ foo z) value)
      ) 
      inner
    )
  )
  (var fn (calc 10 20))

  (fn 30)
)
`,
  160
);

// 匿名函数
testFn(
  eva,
  `
  (begin
    (def onClick (callback) 
      (begin
        (var x 10)
        (var y 20)
        (callback (+ x y))
      )
    )  
    (onClick (lambda (data) (* data 10)))
  )
`,
  300
);

// IIFE：Immediately-invoked lambda expression:
testFn(
  eva,
  `
  ((lambda (x) (* x x)) 2)
`,
  4
);

// Save lambda to variable:
testFn(
  eva,
  `
  (begin
    (var square (lambda (x) (* x x)))
    (square 2)
  )
`,
  4
);

// Recursive function：递归函数
testFn(
  eva,
  `
(begin
  (def factorial (x)
  (if (= x 1)
  1
  (* x (factorial (- x 1)))))

  (factorial 5)
)
`,
  120
);

console.log("All assertions parssed!!!");
